#include "jnumitemscroll.h"
#include <QTECore/qteglobal.h>
#include <QTEGui/juvlmengine.h>

#include <QMouseEvent>
#include <QPainter>

#include <cmath>

namespace QTE
{

/*
	做做简单的数学题
*/

#define PRECISION_LENGTH_I  100000

#define PRECISION_LENGTH_D  0.00001

#define PRECISION(x)    (x+PRECISION_LENGTH_D/10)

#define PRECISION_R(x)  (x-PRECISION_LENGTH_D/10)

class Q_DECL_HIDDEN JNumItemScrollPrivate
{
public:
	explicit JNumItemScrollPrivate(JNumItemScroll *q_ptr);

public:
	void adjusting(bool upperEnd);
	void valueCheck();

	void minWitdhCheck();
	void comparison(qreal old);
	void AV(bool upperEnd);

public:
	void add();
	void sub();

private:
	void zeroAdjusting();

public:
	JNumItemScroll *q_ptr;
	qreal m_precision = 1.0; //精度

	qreal m_currValue = 0.0; //当前值
	qreal m_minValue = 0.0;  //最小值
	qreal m_maxValue = 10.0; //最大值

	bool m_isDragging = false; //鼠标按下标志位
	qreal m_dragValue = 0.0; //鼠标按下时的值

	int m_mousePress = 0; //鼠标拖动值
	int m_deviation = 0;  //显示偏移量
};

JNumItemScrollPrivate::JNumItemScrollPrivate(JNumItemScroll *q_ptr) :
	q_ptr(q_ptr)
{

}

void JNumItemScrollPrivate::adjusting(bool upperEnd)
{
	qreal dv = m_maxValue - m_minValue;

	if( m_precision > PRECISION(dv) )
		m_precision = dv;

	int tmp = J_SCT(int, dv / m_precision + PRECISION_LENGTH_D / 2);

	if( upperEnd )
		m_maxValue = m_minValue + tmp * m_precision;

	else
		m_minValue = m_maxValue - tmp * m_precision;
}

void JNumItemScrollPrivate::valueCheck()
{
	/*
		|-|-|-|-|-|-|-|-|-|->
		0 1 2 3 4 5 6 7 8 9
			|   | |
			 /    \
		leftDV    rightDV
	*/
	if( m_currValue < PRECISION_R(m_minValue) )
		m_currValue = m_minValue;

	else if( m_currValue > PRECISION(m_maxValue) )
		m_currValue = m_maxValue;

	else
	{
		qreal leftDV = fmod(m_currValue, m_precision);

		if( leftDV > PRECISION_LENGTH_D or leftDV < -PRECISION_LENGTH_D )
		{
			qreal rightDV = m_precision - leftDV;

			if( leftDV > PRECISION(rightDV) )
				m_currValue += rightDV;

			else
				m_currValue -= rightDV;

			zeroAdjusting();
		}
	}

	m_deviation = 0;
	q_ptr->update();
}

void JNumItemScrollPrivate::minWitdhCheck()
{
	int size = (q_ptr->height() - 1) / 12; //偏移量越大，数字越小
	QFontMetrics fontMetrics(QFont(q_ptr->font().family(), size * 3 >> 1, 750));

	int minIP = J_SCT(int, m_minValue);
	int maxIP = J_SCT(int, m_maxValue);

	int minValueWidth = fontMetrics.boundingRect(QString("%1").arg(minIP)).width();
	int maxValueWidth = fontMetrics.boundingRect(QString("%1").arg(maxIP)).width();

	int result = 0;

	if( minValueWidth < maxValueWidth )
	{
		maxValueWidth = fontMetrics.boundingRect(QString("%1").arg(m_maxValue)).width();
		int maxValueWidth2 = fontMetrics.boundingRect(QString("%1").arg(m_maxValue - m_precision)).width();

		result = maxValueWidth > maxValueWidth2? maxValueWidth : maxValueWidth2;
	}

	else
	{
		minValueWidth = fontMetrics.boundingRect(QString("%1").arg(m_minValue)).width();
		int minValueWidth2 = fontMetrics.boundingRect(QString("%1").arg(m_minValue + m_precision)).width();

		result = minValueWidth > minValueWidth2? minValueWidth : minValueWidth2;
	}

	int lrOffset = (q_ptr->width() - 1) >> 3;
	if( lrOffset <= 0 )
		lrOffset = 1;

	q_ptr->setMinimumWidth(result + lrOffset);
}

void JNumItemScrollPrivate::comparison(qreal old)
{
	qreal error = old - m_currValue;

	if( error > PRECISION_LENGTH_D / 10 or error < -PRECISION_LENGTH_D / 10 )
		emit q_ptr->currentValueChanged(m_currValue);
}

void JNumItemScrollPrivate::AV(bool upperEnd)
{
	qreal old = m_currValue;

	adjusting(upperEnd);
	valueCheck();

	minWitdhCheck();
	comparison(old);
}

inline void JNumItemScrollPrivate::add()
{
	m_currValue += m_precision;
	zeroAdjusting();
}

inline void JNumItemScrollPrivate::sub()
{
	m_currValue -= m_precision;
	zeroAdjusting();
}

inline void JNumItemScrollPrivate::zeroAdjusting()
{
	if( m_currValue < PRECISION_LENGTH_D and m_currValue > -PRECISION_LENGTH_D )
		m_currValue = 0.0;
}

/*----------------------------------------------------------------------------------*/

JNumItemScroll::JNumItemScroll(QWidget *parent) :
	QFrame(parent),
	d_ptr(new JNumItemScrollPrivate(this))
{
	setFrameShape(Box);
	d_ptr->minWitdhCheck();
}

JNumItemScroll::JNumItemScroll(qreal min, qreal max, bool upperEndAdjusting, QWidget *parent) :
	JNumItemScroll(parent)
{
	setRange(min, max, upperEndAdjusting);
}

JNumItemScroll::JNumItemScroll(qreal precision, qreal min, qreal max, bool upperEndAdjusting, QWidget *parent) :
	JNumItemScroll(parent)
{
	setRange(min, max, upperEndAdjusting);
	setPrecision(precision, upperEndAdjusting);
}

JNumItemScroll::~JNumItemScroll()
{
	delete d_ptr;
}

qreal JNumItemScroll::precision() const
{
	return d_ptr->m_precision;
}

qreal JNumItemScroll::minimumValue() const
{
	return d_ptr->m_minValue;
}

qreal JNumItemScroll::maximumValue() const
{
	return d_ptr->m_maxValue;
}

qreal JNumItemScroll::currentValue() const
{
	return d_ptr->m_currValue;
}

bool JNumItemScroll::gestureEnabled() const
{
	return JUVLMEngine::globalInstance().isBind(this);
}

void JNumItemScroll::setPrecision(qreal precision, bool upperEndAdjusting)
{
	int IP = J_SCT(int, precision);
	qreal DP = precision - IP;

	int tmp = J_SCT(int, DP * PRECISION_LENGTH_I);

	if( IP == 0 )
	{
		if( tmp == 0 )
			tmp = 1;

		d_ptr->m_precision = tmp * PRECISION_LENGTH_D;
	}
	else
		d_ptr->m_precision = IP + tmp * PRECISION_LENGTH_D;

	d_ptr->AV(upperEndAdjusting);
}

void JNumItemScroll::setRange(qreal min, qreal max, bool upperEndAdjusting)
{
	d_ptr->m_minValue = min;
	d_ptr->m_maxValue = max;

	d_ptr->AV(upperEndAdjusting);
}

void JNumItemScroll::setMinimumValue(qreal min)
{
	d_ptr->m_minValue = min;
	d_ptr->AV(true);
}

void JNumItemScroll::setMaximumValue(qreal max)
{
	d_ptr->m_maxValue = max;
	d_ptr->AV(false);
}

void JNumItemScroll::setValue(qreal value)
{
	qreal old = d_ptr->m_currValue;

	d_ptr->m_currValue = value;
	d_ptr->valueCheck();

	d_ptr->minWitdhCheck();
	d_ptr->comparison(old);
}

void JNumItemScroll::setGestureEnabled(bool enable)
{
	if( enable )
	{
		JUVLMEngine::Bind bind;
		bind.widget = this;

		bind.actionTriggered = [this](const QPoint &pos)
		{
			d_ptr->m_deviation  = 0;
			d_ptr->m_isDragging = true;
			d_ptr->m_mousePress = pos.y();
			d_ptr->m_dragValue  = d_ptr->m_currValue;
		};

		bind.running = [this](const QPoint &delta) -> bool
		{
			d_ptr->m_deviation -= delta.y();

			int _height = height() - 1;//获取界面长

			if( d_ptr->m_deviation > _height / 3 )
				d_ptr->m_deviation = _height / 3;

			if( d_ptr->m_deviation < -_height / 3 )
				d_ptr->m_deviation = -_height / 3;

			update();//更新显示
			return true;
		};

		bind.stop = [this]()
		{
			d_ptr->m_isDragging = false;
			d_ptr->m_deviation  = 0;

			update();
			d_ptr->comparison(d_ptr->m_dragValue);
		};

		JUVLMEngine::globalInstance().bind(bind);
	}

	else
		JUVLMEngine::globalInstance().unbound(this);
}

void JNumItemScroll::wheelEvent(QWheelEvent *event)//滚轮
{
	bool changed = false;

#if QT_VERSION >= QT_VERSION_CHECK(5,15,0)
	if( event->angleDelta().y() > 0 ) //向上滚轮
#else
	if( event->delta() > 0 ) //向上滚轮
#endif //Qt-5.15
	{
		if( d_ptr->m_currValue > PRECISION(d_ptr->m_minValue) )
		{
			changed = true;
			d_ptr->sub();
		}
	}

	else if( d_ptr->m_currValue < PRECISION_R(d_ptr->m_maxValue) )
	{
		changed = true;
		d_ptr->add();
	}

	d_ptr->m_deviation = 0;
	update();//更新显示

	if( changed )
		emit currentValueChanged(d_ptr->m_currValue);

	QFrame::wheelEvent(event);
}

void JNumItemScroll::paintEvent(QPaintEvent *event)
{
	int _height = height() - 1;

	qreal minValue = PRECISION(d_ptr->m_minValue);
	qreal maxValue = PRECISION_R(d_ptr->m_maxValue);

	if( d_ptr->m_deviation >= _height / 6 )
	{
		if( d_ptr->m_currValue > minValue )
		{
			d_ptr->m_mousePress += _height / 3;//偏移初值
			d_ptr->m_deviation -= _height / 3;
			d_ptr->sub();
		}
		else
			JUVLMEngine::globalInstance().stopMotion(this);
	}

	else if( d_ptr->m_deviation <= -_height / 6 )
	{
		if( d_ptr->m_currValue < maxValue )
		{
			d_ptr->m_mousePress -= _height / 3;//偏移初值
			d_ptr->m_deviation += _height / 3;
			d_ptr->add();
		}
		else
			JUVLMEngine::globalInstance().stopMotion(this);
	}

	QPainter painter(this);
	painter.setRenderHints(QPainter::Antialiasing, true); //创建画板
	painter.save(); //保存当前绘制器状态
	painter.setBrush(QColor(200,200,200)); //画板颜色
	painter.restore(); //恢复当前的绘制状态

	auto font = paintNum(painter, d_ptr->m_currValue, d_ptr->m_deviation); //显示当前值

	if( d_ptr->m_isDragging == false )
	{
		QFontMetrics fontMetrics(font);

		auto rect = fontMetrics.boundingRect(QString("%1").arg(d_ptr->m_currValue));
		setMinimumWidth(rect.width() + 9);
	}

	if( d_ptr->m_currValue > minValue )
		paintNum(painter, d_ptr->m_currValue - d_ptr->m_precision, d_ptr->m_deviation - _height / 3);//显示上一个值

	if( d_ptr->m_currValue < maxValue )
		paintNum(painter, d_ptr->m_currValue + d_ptr->m_precision, d_ptr->m_deviation + _height / 3);//显示下一个值

	// 补空位的一个显示，让过度平滑
	if( d_ptr->m_deviation > 0 ) //>0是-1，<0是+1
	{
		if( d_ptr->m_currValue > minValue + d_ptr->m_precision ) //显示-2的数字
			paintNum(painter, d_ptr->m_currValue - 2.0 * d_ptr->m_precision, (-_height / 3 << 1) + d_ptr->m_deviation);
	}
	else if( d_ptr->m_currValue < maxValue - d_ptr->m_precision ) //显示+2的数字
		paintNum(painter, d_ptr->m_currValue + 2.0 * d_ptr->m_precision, (_height / 3 << 1) + d_ptr->m_deviation);

	// 画两个横杠，画面效果
	QPen pen;
	pen.setColor(QColor(40,40,40)); //颜色
	pen.setWidth(_height / 40); //线宽
	painter.setPen(pen); //拿起画笔

	// 画两条线
	int _width = width() - 1;//获取长和宽
	int lrOffset = _width >> 3;
	if( lrOffset < 1 )
		lrOffset = 1;

	painter.drawLine(QPoint(lrOffset, _height / 3), QPoint(_width - lrOffset, _height / 3));
	painter.drawLine(QPoint(lrOffset, _height / 3 << 1), QPoint(_width - lrOffset, _height / 3 << 1));

	QFrame::paintEvent(event);
}

void JNumItemScroll::changeEvent(QEvent *event)
{
	QFrame::changeEvent(event);
	if( event->type() == QEvent::FontChange )
		d_ptr->minWitdhCheck();
}

// 画笔的指针，显示的数字，偏移量（+向下-向上）
QFont JNumItemScroll::paintNum(QPainter &painter, qreal num, int deviation)
{
	int _height = height() - 1;

	int size = (_height - qAbs(deviation)) / 12; //偏移量越大，数字越小
	int transparency = 255 - 255 * qAbs(deviation) / _height; //偏移量越大，透明度越高

	int heightThird = _height / 3;
	int y = (_height >> 1) + deviation - (heightThird >> 1);

	painter.setPen(QColor(0,0,0,transparency));//设置颜色和透明度

	auto font = QFont(painter.font().family(), size * 3 >> 1, 750);
	painter.setFont(font); //设置字体大小

	if( num < PRECISION_LENGTH_D and num > -PRECISION_LENGTH_D )
		num = 0.0;

	painter.drawText(QRectF(0, y, width() - 1, heightThird), Qt::AlignCenter, QString::number(num));//写字
	return font;
}

} //namespace QTE
